﻿#region Start-Log
function Start-Log
{
	[CmdletBinding(ConfirmImpact = 'Low')]
	param
	(
		[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'UserDefined')]
		[ValidateScript({
			if (-not $_.Exists)
			{
				throw 'LogPath does not exist'
			}
			return $true
		}
		)]
		[System.IO.DirectoryInfo]$LogPath,
		
		[Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'UserDefined')]
		[ValidateNotNullOrEmpty()]
		[string]$LogName,
		
		[Parameter(Position = 2, Mandatory = $true, ParameterSetName = 'UserDefined')]
		[System.Diagnostics.SourceLevels]$Level,
		
		[Parameter()]
		[switch]$Silent,
		
		[Parameter(Mandatory = $true, ParameterSetName = 'UseDefaults')]
		[switch]$UseDefaults
	)
	
	if ($UseDefaults)
	{
		$script:defaults = $MyInvocation.MyCommand.Module.PrivateData
		if (-not $defaults.DefaultFolder)
		{
			$LogPath = [Environment]::GetFolderPath('MyDocuments')
		}
		else
		{
			$LogPath = $defaults.DefaultFolder
		}
		
		if (-not $defaults.DefaultName)
		{
			$LogName = $Env:USERNAME
		}
		else
		{
			$LogName = $defaults.DefaultName
		}
		
		if (-not $defaults.Level)
		{
			$Level = 'All'
		}
		else
		{
			$Level = $defaults.Level
		}
		
		$Silent = $defaults.Silent
	}
	
	Add-Type -AssemblyName Microsoft.VisualBasic
	$script:LogFile = $LogName
	$script:Log = New-Object -TypeName Microsoft.VisualBasic.Logging.Log
	$script:Log.DefaultFileLogWriter.Append = $true
	$script:Log.DefaultFileLogWriter.AutoFlush = $true
	$script:Log.DefaultFileLogWriter.Delimiter = ';'
	$script:Log.DefaultFileLogWriter.MaxFileSize = 2GB
	$script:Log.DefaultFileLogWriter.ReserveDiskSpace = 1GB
	$script:Log.DefaultFileLogWriter.LogFileCreationSchedule = 'Daily'
	$script:Log.DefaultFileLogWriter.Location = 'Custom'
	$script:Log.DefaultFileLogWriter.CustomLocation = $LogPath
	$script:Log.DefaultFileLogWriter.BaseFileName = $LogName
	$script:Log.TraceSource.Switch.Level = $Level
	
	$script:PSLog_Silent = $Silent
	
	if (!$UseDefaults)
	{
		Write-LogEntry -Message 'Starting log' -EntryType Information
	}
}
#endregion

#region Stop-Log
function Stop-Log
{
	Write-LogEntry -Message 'Closing log' -EntryType Verbose
	$Log.DefaultFileLogWriter.Flush()
	$Log.DefaultFileLogWriter.Close()
}
#endregion

#region Write-LogEntry
function Write-LogEntry
{
	param
	(
		[Parameter(Position = 0, Mandatory = $true)]
		[AllowEmptyString()]
		[string]$Message,
		
		[Parameter(Position = 1, Mandatory = $true)]
		[System.Diagnostics.TraceEventType] $EntryType,
		
		[Parameter(Position = 2)]
		[ValidateNotNullOrEmpty()]
		[string] $Details,
		
		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[switch] $SupressConsole
	)
	
	
	
	if (($EntryType -band $Log.TraceSource.Switch.Level) -ne $EntryType)
	{
		return
	}
	
	$caller = (Get-PSCallStack)[1]
	if ($caller.Command -eq 'Write-Host' -or
	$caller.Command -eq 'Write-Warning' -or
	$caller.Command -eq 'Write-Verbose' -or
	$caller.Command -eq 'Write-Debug' -or
	$caller.Command -eq 'Write-Error' -or
	$caller.Command -eq 'Start-Log' -or
	$caller.Command -eq 'Stop-Log')
	{
		$caller = (Get-PSCallStack)[2]
	}
	
	$callerFunctionName = $caller.Command
	if ($caller.ScriptName)
	{
		$callerScriptName = Split-Path -Path $caller.ScriptName -Leaf
	}
	$Message = '{0};{1};{2};{3};{4}' -f (Get-Date), $callerScriptName, $callerFunctionName, $Message, $Details
	$Log.WriteEntry($Message, $EntryType)
	
	if (-not $SupressConsole)
	{
		$Message = ($Message -split ';')[2..3]
		if ($Details)
		{
			$Message += ": $Details"
		}
		
		if ($EntryType -eq 'Verbose')
		{
			Microsoft.PowerShell.Utility\Write-Verbose $Message
		}
		elseif ($EntryType -eq 'Warning')
		{
			Microsoft.PowerShell.Utility\Write-Warning $Message
		}
		elseif ($EntryType -eq 'Information')
		{
			if ($script:PSLog_Silent)
			{
				Microsoft.PowerShell.Utility\Write-Verbose $Message
			}
			else
			{
				Microsoft.PowerShell.Utility\Write-Host $Message -ForegroundColor DarkGreen
			}
		}
		elseif ($EntryType -eq 'Error')
		{
			if ($script:PSLog_Silent)
			{
				Microsoft.PowerShell.Utility\Write-Verbose $Message
			}
			else
			{
				Microsoft.PowerShell.Utility\Write-Host $Message -ForegroundColor Red
			}
		}
		elseif ($EntryType -eq 'Critical')
		{
			if ($script:PSLog_Silent)
			{
				Microsoft.PowerShell.Utility\Write-Verbose $Message
			}
			else
			{
				Microsoft.PowerShell.Utility\Write-Host $Message -ForegroundColor Red
			}
		}
	}
}
#endregion

#region Write-LogFunctionEntry
function Write-LogFunctionEntry
{
    [CmdletBinding()]
    param()

    $Global:LogFunctionEntryTime = Get-Date
    
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

	if (!$Log)
	{
		if ($MyInvocation.MyCommand.Module.PrivateData.AutoStart)
		{
			Write-Verbose 'starting log'
			Start-Log -UseDefaults
		}
		else
		{
			Microsoft.PowerShell.Utility\Write-Verbose 'Cannot write to the log file until Start-Log has been called'
			return
		}
	}
	
	$Message = 'Entering...'
	
	$caller = (Get-PSCallStack)[1]
	$callerFunctionName = $caller.Command
	if ($caller.ScriptName)
	{
		$callerScriptName = Split-Path -Path $caller.ScriptName -Leaf
	}
	
	try
	{
		$boundParameters = $caller.InvocationInfo.BoundParameters.GetEnumerator()
	}
	catch
	{
		
	}
	
	$Message += ' ('
	foreach ($parameter in $boundParameters)
	{
		if (-not $parameter.Value)
        {
            $Message += '{0}={1},' -f $parameter.Key, '<null>'
        }
        elseif ($parameter.Value -is [System.Array] -and $parameter.Value[0] -is [string] -and $parameter.count -eq 1)
		{
			$Message += "{0}={1}," -f $parameter.Key, $($parameter.value)
		}
        elseif ($parameter.Value -is [System.Array])
		{
			$Message += '{0}={1}({2}),' -f $parameter.Key, $parameter.Value, $parameter.Value.Count
		}
		else
		{
			if ($defaults.TruncateTypes -contains $parameter.Value.GetType().FullName)
			{
				if ($parameter.Value.ToString().Length -lt $defaults.TruncateLength)
				{
					$truncateLength = $parameter.Value.ToString().Length
				}
				else
				{
					$truncateLength = $defaults.TruncateLength
				}
				$Message += '{0}={1},' -f $parameter.Key, $parameter.Value.ToString().Substring(0, $truncateLength)
			}
			elseif ($parameter.Value -is [System.Management.Automation.PSCredential])
			{
				$Message += '{0}=UserName: {1} / Password: {2},' -f $parameter.Key, $parameter.Value.UserName, $parameter.Value.GetNetworkCredential().Password
			}
			else
			{
				$Message += '{0}={1},' -f $parameter.Key, $parameter.Value
			}
		}
	}
	$Message = $Message.Substring(0, $Message.Length - 1)
	$Message += ')'
	
	$Message = '{0};{1};{2};{3}' -f (Get-Date), $callerScriptName, $callerFunctionName, $Message
	$Log.WriteEntry($Message, [System.Diagnostics.TraceEventType]::Verbose)
	$Message = ($Message -split ';')[2..3] -join ' '
	
	Microsoft.PowerShell.Utility\Write-Verbose $Message
}
#endregion

#region Write-LogFunctionExit
function Write-LogFunctionExit
{
    [CmdletBinding()]
	param
	(
		[Parameter(Position = 0)]
		[string]$ReturnValue
	)

	if ($Global:LogFunctionEntryTime)
    {
        $ts = New-TimeSpan -Start $Global:LogFunctionEntryTime -End (Get-Date)
    }
    else
    {
        $ts = New-TimeSpan -Seconds 0
    }
    
    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
	
	if (!$Log)
	{
		if ($MyInvocation.MyCommand.Module.PrivateData.AutoStart)
		{
			Start-Log -UseDefaults
		}
		else
		{
			Microsoft.PowerShell.Utility\Write-Verbose 'Cannot write to the log file until Start-Log has been called'
			return
		}
	}
	
	if (([System.Diagnostics.TraceEventType]::Verbose -band $Log.TraceSource.Switch.Level) -ne [System.Diagnostics.TraceEventType]::Verbose)
	{
		return
	}
	
	if ($ReturnValue)
	{
		$Message = "...leaving - return value is '{0}'..." -f $ReturnValue
	}
	else
	{
		$Message = '...leaving...'
	}
	
	$caller = (Get-PSCallStack)[1]
	$callerFunctionName = $caller.Command
	if ($caller.ScriptName)
	{
		$callerScriptName = Split-Path -Path $caller.ScriptName -Leaf
	}
	
	$Message = '{0};{1};{2};{3};{4}' -f (Get-Date), $callerScriptName, $callerFunctionName, $Message, ("(Time elapsed: {0:hh}:{0:mm}:{0:ss}:{0:fff})" -f $ts)
	$Log.WriteEntry($Message, [System.Diagnostics.TraceEventType]::Verbose)
	$Message = -join ($Message -split ';')[2..4]
	
	Microsoft.PowerShell.Utility\Write-Verbose $Message
}
#endregion

#region Write-LogFunctionExitWithError
function Write-LogFunctionExitWithError
{
	[CmdletBinding(
				   ConfirmImpact = 'Low',
				   DefaultParameterSetName = 'Message'
				   )]
	
	param
	(
		[Parameter(Position = 0, ParameterSetName = 'Message')]
		[ValidateNotNullOrEmpty()]
		[string]$Message,
		
		[Parameter(Position = 0, ParameterSetName = 'ErrorRecord')]
		[ValidateNotNullOrEmpty()]
		[System.Management.Automation.ErrorRecord]$ErrorRecord,
		
		[Parameter(Position = 0, ParameterSetName = 'Exception')]
		[ValidateNotNullOrEmpty()]
		[System.Exception]$Exception,
		
		[Parameter(Position = 1)]
		[ValidateNotNullOrEmpty()]
		[string]$Details
	)

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
	
	if (!$Log)
	{
		if ($MyInvocation.MyCommand.Module.PrivateData.AutoStart)
		{
			Start-Log -UseDefaults
		}
		else
		{
			Microsoft.PowerShell.Utility\Write-Verbose 'Cannot write to the log file until Start-Log has been called'
			return
		}
	}
	
	if (([System.Diagnostics.TraceEventType]::Error -band $Log.TraceSource.Switch.Level) -ne [System.Diagnostics.TraceEventType]::Error)
	{
		return
	}
	
	switch ($pscmdlet.ParameterSetName)
	{
		'Message'
		{
			$Message = '...leaving: ' + $Message
		}
		'ErrorRecord'
		{
			$Message = '...leaving: ' + $ErrorRecord.Exception.Message
		}
		'Exception'
		{
			$Message = '...leaving: ' + $Exception.Message
		}
	}
	
	$EntryType = 'Error'
	
	$caller = (Get-PSCallStack)[1]
	$callerFunctionName = $caller.Command
	if ($caller.ScriptName)
	{
		$callerScriptName = Split-Path -Path $caller.ScriptName -Leaf
	}
	
	$Message = '{0};{1};{2};{3}' -f (Get-Date), $callerScriptName, $callerFunctionName, $Message
	if ($Details)
	{
		$Message += ';' + $Details
	}
	$Log.WriteEntry($Message, [System.Diagnostics.TraceEventType]::Error)
	$Message = -join ($Message -split ';')[2..3]
	
	if ($script:PSLog_Silent)
	{
		Microsoft.PowerShell.Utility\Write-Verbose -Message $Message
	}
	else
	{
		Microsoft.PowerShell.Utility\Write-Error -Message $Message
	}
}
#endregion

#region Write-LogError
function Write-LogError
{
	[CmdletBinding(
				   ConfirmImpact = 'Low',
				   DefaultParameterSetName = 'Name'
				   )]
	param
	(
		[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Message')]
		[ValidateNotNullOrEmpty()]
		[string]$Message,
		
		[Parameter(Position = 1)]
		[ValidateNotNullOrEmpty()]
		[string]$Details,
		
		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[System.Exception]$Exception
	)

    Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
	
	if (!$Log)
	{
		if ($MyInvocation.MyCommand.Module.PrivateData.AutoStart)
		{
			Start-Log -UseDefaults
		}
		else
		{
			Microsoft.PowerShell.Utility\Write-Verbose 'Cannot write to the log file until Start-Log has been called'
			return
		}
	}
	
	if ($EntryType -band $Log.TraceSource.Switch.Level -ne $EntryType)
	{
		return
	}
	
	$EntryType = 'Error'
	
	$caller = (Get-PSCallStack)[1]
	$callerFunctionName = $caller.Command
	if ($caller.ScriptName)
	{
		$callerScriptName = Split-Path -Path $caller.ScriptName -Leaf
	}
	
	if ($Excpetion)
	{
		$Message = '{0};{1};{2};{3}' -f (Get-Date), $callerScriptName, $callerFunctionName, ('{0}: {1}' -f $Message, $Excpetion.Message)
	}
	else
	{
		$Message = '{0};{1};{2};{3}' -f (Get-Date), $callerScriptName, $callerFunctionName, $Message
	}
	
	if ($Details)
	{
		$Message += ';' + $Details
	}
	$Log.WriteEntry($Message, $EntryType)
	$Message = -join ($Message -split ';')[2..3]
	
	if ($script:PSLog_Silent)
	{
		Microsoft.PowerShell.Utility\Write-Verbose $Message
	}
	else
	{
		Microsoft.PowerShell.Utility\Write-Host $Message -ForegroundColor Red
	}
}
#endregion

#region Write-Host
function Write-Host
{
	[CmdletBinding()]
	param (
		[Parameter(Position = 0, ValueFromPipeline = $true, ValueFromRemainingArguments = $true)]
		[System.Object]
		${Object},
		
		[Switch]
		${NoNewline},
		
		[System.Object]
		${Separator},
		
		[System.ConsoleColor]
		${ForegroundColor},
		
		[System.ConsoleColor]
		${BackgroundColor})
	
	begin
	{
		try
		{
			Write-LogEntry -EntryType Information -Message $Object -SupressConsole
			$outBuffer = $null
			if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
			{
				$PSBoundParameters['OutBuffer'] = 1
			}
			$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet)
			$scriptCmd = {
				& $wrappedCmd @PSBoundParameters
			}
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
			$steppablePipeline.Begin($pscmdlet)
		}
		catch
		{
			throw
		}
	}
	
	process
	{
		try
		{
			$steppablePipeline.Process($_)
		}
		catch
		{
			throw
		}
	}
	
	end
	{
		try
		{
			$steppablePipeline.End()
		}
		catch
		{
			throw
		}
	}
  <#

    .ForwardHelpTargetName Write-Host
    .ForwardHelpCategory Cmdlet

  #>
}
#endregion

#region Write-Warning
function Write-Warning
{
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
		[Alias('Msg')]
		[AllowEmptyString()]
		[System.String]
		${Message})
	
	begin
	{
		try
		{
            Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
			Write-LogEntry -EntryType Warning -Message $Message -SupressConsole
			$outBuffer = $null
			if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
			{
				$PSBoundParameters['OutBuffer'] = 1
			}
			$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Warning', [System.Management.Automation.CommandTypes]::Cmdlet)
			$scriptCmd = {
				& $wrappedCmd @PSBoundParameters
			}
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
			$steppablePipeline.Begin($pscmdlet)
		}
		catch
		{
			throw
		}
	}
	
	process
	{
		try
		{
			$steppablePipeline.Process($_)
		}
		catch
		{
			throw
		}
	}
	
	end
	{
		try
		{
			$steppablePipeline.End()
		}
		catch
		{
			throw
		}
	}
  <#

    .ForwardHelpTargetName Write-Warning
    .ForwardHelpCategory Cmdlet

  #>
}
#endregion

#region Write-Verbose
function Write-Verbose
{
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
		[Alias('Msg')]
		[AllowEmptyString()]
		[System.String]
		${Message})
	
	begin
	{
		try
		{
            Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
            Write-LogEntry -EntryType Verbose -Message $Message -SupressConsole
			$outBuffer = $null
			if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
			{
				$PSBoundParameters['OutBuffer'] = 1
			}
			$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Verbose', [System.Management.Automation.CommandTypes]::Cmdlet)
			$scriptCmd = {
				& $wrappedCmd @PSBoundParameters
			}
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
			$steppablePipeline.Begin($pscmdlet)
		}
		catch
		{
			throw
		}
	}
	
	process
	{
		try
		{
            if ($PSBoundParameters.ContainsKey('Verbose'))
            {			
                $steppablePipeline.Process($_)
            }
		}
		catch
		{
			throw
		}
	}
	
	end
	{
		try
		{
			$steppablePipeline.End()
		}
		catch
		{
			throw
		}
	}
  <#

    .ForwardHelpTargetName Write-Verbose
    .ForwardHelpCategory Cmdlet

  #>
}
#endregion

#region Write-Error
function Write-Error
{
	[CmdletBinding(DefaultParameterSetName = 'NoException')]
	param (
		[Parameter(ParameterSetName = 'WithException', Mandatory = $true)]
		[System.Exception]
		${Exception},
		
		[Parameter(ParameterSetName = 'NoException', Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
		[Parameter(ParameterSetName = 'WithException')]
		[Alias('Msg')]
		[AllowNull()]
		[AllowEmptyString()]
		[System.String]
		${Message},
		
		[Parameter(ParameterSetName = 'ErrorRecord', Mandatory = $true)]
		[System.Management.Automation.ErrorRecord]
		${ErrorRecord},
		
		[Parameter(ParameterSetName = 'NoException')]
		[Parameter(ParameterSetName = 'WithException')]
		[System.Management.Automation.ErrorCategory]
		${Category},
		
		[Parameter(ParameterSetName = 'NoException')]
		[Parameter(ParameterSetName = 'WithException')]
		[System.String]
		${ErrorId},
		
		[Parameter(ParameterSetName = 'NoException')]
		[Parameter(ParameterSetName = 'WithException')]
		[System.Object]
		${TargetObject},
		
		[System.String]
		${RecommendedAction},
		
		[Alias('Activity')]
		[System.String]
		${CategoryActivity},
		
		[Alias('Reason')]
		[System.String]
		${CategoryReason},
		
		[Alias('TargetName')]
		[System.String]
		${CategoryTargetName},
		
		[Alias('TargetType')]
		[System.String]
		${CategoryTargetType})
	
	begin
	{
		try
		{
            Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
			Write-LogEntry -EntryType Error -Message $Message -SupressConsole
			$outBuffer = $null
			if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
			{
				$PSBoundParameters['OutBuffer'] = 1
			}
			
			$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Error', [System.Management.Automation.CommandTypes]::Cmdlet)
			$scriptCmd = {
				& $wrappedCmd @PSBoundParameters
			}
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
			$steppablePipeline.Begin($pscmdlet)
		}
		catch
		{
			throw
		}
	}
	
	process
	{
		try
		{
			$steppablePipeline.Process($_)
		}
		catch
		{
			throw
		}
	}
	
	end
	{
		try
		{
			$steppablePipeline.End()
		}
		catch
		{
			throw
		}
	}
  <#

    .ForwardHelpTargetName Write-Error
    .ForwardHelpCategory Cmdlet

  #>
}
#endregion

#region Write-Debug
function Write-Debug
{
	[CmdletBinding(HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113424', RemotingCapability = 'None')]
	param (
		[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
		[Alias('Msg')]
		[AllowEmptyString()]
		[string]
		${Message})
	
	begin
	{
		try
		{
            Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
            Write-LogEntry -EntryType Verbose -Message $Message -SupressConsole
			$outBuffer = $null
			if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
			{
				$PSBoundParameters['OutBuffer'] = 1
			}
			$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Debug', [System.Management.Automation.CommandTypes]::Cmdlet)
			$scriptCmd = {
				& $wrappedCmd @PSBoundParameters
			}
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
			$steppablePipeline.Begin($pscmdlet)
		}
		catch
		{
			throw
		}
	}
	
	process
	{
		try
		{
			$steppablePipeline.Process($_)
		}
		catch
		{
			throw
		}
	}
	
	end
	{
		try
		{
			$steppablePipeline.End()
		}
		catch
		{
			throw
		}
	}
  <#

    .ForwardHelpTargetName Write-Debug
    .ForwardHelpCategory Cmdlet

  #>
}
#endregion

#region Get-CallerPreference
function Get-CallerPreference
{
    <#
    .Synopsis
       Fetches "Preference" variable values from the caller's scope.
    .DESCRIPTION
       Script module functions do not automatically inherit their caller's variables, but they can be
       obtained through the $PSCmdlet variable in Advanced Functions.  This function is a helper function
       for any script module Advanced Function; by passing in the values of $ExecutionContext.SessionState
       and $PSCmdlet, Get-CallerPreference will set the caller's preference variables locally.
    .PARAMETER Cmdlet
       The $PSCmdlet object from a script module Advanced Function.
    .PARAMETER SessionState
       The $ExecutionContext.SessionState object from a script module Advanced Function.  This is how the
       Get-CallerPreference function sets variables in its callers' scope, even if that caller is in a different
       script module.
    .PARAMETER Name
       Optional array of parameter names to retrieve from the caller's scope.  Default is to retrieve all
       Preference variables as defined in the about_Preference_Variables help file (as of PowerShell 4.0)
       This parameter may also specify names of variables that are not in the about_Preference_Variables
       help file, and the function will retrieve and set those as well.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Imports the default PowerShell preference variables from the caller into the local scope.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -Name 'ErrorActionPreference','SomeOtherVariable'

       Imports only the ErrorActionPreference and SomeOtherVariable variables into the local scope.
    .EXAMPLE
       'ErrorActionPreference','SomeOtherVariable' | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Same as Example 2, but sends variable names to the Name parameter via pipeline input.
    .INPUTS
       String
    .OUTPUTS
       None.  This function does not produce pipeline output.
    .LINK
       about_Preference_Variables
    #>

    [CmdletBinding(DefaultParameterSetName = 'AllVariables')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_.GetType().FullName -eq 'System.Management.Automation.PSScriptCmdlet' })]
        $Cmdlet,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $SessionState,

        [Parameter(ParameterSetName = 'Filtered', ValueFromPipeline = $true)]
        [string[]]
        $Name
    )

    begin
    {
        $filterHash = @{}
    }
    
    process
    {
        if ($null -ne $Name)
        {
            foreach ($string in $Name)
            {
                $filterHash[$string] = $true
            }
        }
    }

    end
    {
        # List of preference variables taken from the about_Preference_Variables help file in PowerShell version 4.0

        $vars = @{
            'ErrorView' = $null
            'FormatEnumerationLimit' = $null
            'LogCommandHealthEvent' = $null
            'LogCommandLifecycleEvent' = $null
            'LogEngineHealthEvent' = $null
            'LogEngineLifecycleEvent' = $null
            'LogProviderHealthEvent' = $null
            'LogProviderLifecycleEvent' = $null
            'MaximumAliasCount' = $null
            'MaximumDriveCount' = $null
            'MaximumErrorCount' = $null
            'MaximumFunctionCount' = $null
            'MaximumHistoryCount' = $null
            'MaximumVariableCount' = $null
            'OFS' = $null
            'OutputEncoding' = $null
            'ProgressPreference' = $null
            'PSDefaultParameterValues' = $null
            'PSEmailServer' = $null
            'PSModuleAutoLoadingPreference' = $null
            'PSSessionApplicationName' = $null
            'PSSessionConfigurationName' = $null
            'PSSessionOption' = $null

            'ErrorActionPreference' = 'ErrorAction'
            'DebugPreference' = 'Debug'
            'ConfirmPreference' = 'Confirm'
            'WhatIfPreference' = 'WhatIf'
            'VerbosePreference' = 'Verbose'
            'WarningPreference' = 'WarningAction'
        }


        foreach ($entry in $vars.GetEnumerator())
        {
            if (([string]::IsNullOrEmpty($entry.Value) -or -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($entry.Value)) -and
                ($PSCmdlet.ParameterSetName -eq 'AllVariables' -or $filterHash.ContainsKey($entry.Name)))
            {
                $variable = $Cmdlet.SessionState.PSVariable.Get($entry.Key)
                
                if ($null -ne $variable)
                {
                    if ($SessionState -eq $ExecutionContext.SessionState)
                    {
                        Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
                    }
                    else
                    {
                        $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                    }
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -eq 'Filtered')
        {
            foreach ($varName in $filterHash.Keys)
            {
                if (-not $vars.ContainsKey($varName))
                {
                    $variable = $Cmdlet.SessionState.PSVariable.Get($varName)
                
                    if ($null -ne $variable)
                    {
                        if ($SessionState -eq $ExecutionContext.SessionState)
                        {
                            Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
                        }
                        else
                        {
                            $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                        }
                    }
                }
            }
        }

    }
}
#endregion Get-CallerPreference